package org.cmcc.pcap.jpcap.capture;

import com.sun.jna.NativeLibrary;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.cmcc.pcap.jpcap.util.CommonUtils;
import org.cmcc.pcap.jpcap.util.PacketAnalysisConfig;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.Properties;

public class JCaptures {
	private static Logger logger = LogManager.getLogger(JCaptures.class);
	
//	public boolean complete = false;//标识单次一个pcap文件处理过程是否全部完成
	
	//标记测试数据包中是否有tcp包或者udp包，true表示有，false表示没有
	public boolean tcp_packet_exist;
	public boolean udp_packet_exist;
	//标记tcp或者udp包是否处理完毕
	public boolean tcp_complete;
	public boolean udp_complete;
	//此处是否要新增两个标记位，一个用来标识tcp，一个用来标识udp
	public boolean fileRealSerial = false ;
	public String filter;
	private PacketHandler packetHandler;
	JpcapCaptor captor ;
  
//20180420
//	private static JCaptures jCaptures;
//	public static JCaptures getInstance(){//懒汉...需要线程安全吗？
//		jCaptures = jCaptures == null ? new JCaptures() : jCaptures;
//		return jCaptures;
//	}	
	

	public JCaptures(String taskId){
		logger.info("< I just tell you , I have started >");
		logger.debug("\n < System Env >  : \n"+System.getenv().toString().replaceAll(",", "\n"));
		logger.debug("\n < System Properties >  : \n"+System.getProperties().toString().replaceAll(",", "\n"));
		prepareDependantDll();
		this.filter = PacketAnalysisConfig.getConfigAsString("jpcap.filter").trim();
		this.packetHandler = new PacketHandler(this,taskId);
		this.fileRealSerial = "FileRealSerial".equalsIgnoreCase(PacketAnalysisConfig.getConfigAsString("pcap.deal.mode"));
		logger.info("FileRealSerial : ["+fileRealSerial+"]");
		logger.info("Jcap Filter : ["+filter+"]");
	}
	
	
	/**
	 * 停止抓取包,结束所有处理线程
	 */
	public void stop(){
		logger.info("[ stoping JCaptures ]");
		if(captor != null)
		    captor.close();
		if(this.packetHandler != null){
			this.packetHandler.stop();
			this.packetHandler = null;
		}
		logger.info("[ stoped JCaptures ]");
	}
	/**
	 * 从网卡抓包
	 * @param deviceInfo
	 */
	public void captureFromDevice(String deviceInfo, PacketHandler packetHandler){
//		if(packetHandler != null) 
//		    this.packetHandler = packetHandler;//允许接口调用者使用自己的packetHandler
//		else
//			this.packetHandler = new PacketHandler();//如果接口调用者没有传入packetHandler,new一个默认的packetHandler
		
		NetworkInterface device = findDevice(deviceInfo);
		if(device != null){
			try {
				captor = JpcapCaptor.openDevice(device, 65535, false, 20);
				/**
				 * captor.setFilter(arg0, arg1);//TODO 设置过滤器,构建一些常用的过滤规则？
				 * captor.setFilter("tcp", true);
				 * jpcap.setFilter("tcp and src 172.16.101.99", true);
				 * 如何过滤HTTP？
				 */
				captor.setFilter("tcp", true);
				captor.loopPacket(-1, this.packetHandler);//此处循环抓包，如果是从网卡抓，会一直在这执行

			} catch (IOException e) {
				e.printStackTrace();
			}
		}else{
			logger.error("capture start failure  with deviceinfo :"+deviceInfo);
		}
		
		
	}
	/**
	  *  从单个pcap文件读取抓包. TODO 判断文件后缀类型？
	  *  外部多次调用该方法时，每个文件串行处理
	 * @param filePath
	 */
	public void captureFromFile(String filePath,PacketHandler packetHandler){
//		if(packetHandler != null) 
//		    this.packetHandler = packetHandler;//允许接口调用者使用自己的packetHandler
//		else
//			this.packetHandler = new PacketHandler();//如果接口调用者没有传入packetHandler,new一个默认的packetHandler
		
		logger.debug("this is udp test !");
		try {
			captor = JpcapCaptor.openFile(filePath);
			/**
			 * captor.setFilter(arg0, arg1);//TODO 设置过滤器,构建一些常用的过滤规则？
			 * captor.setFilter("tcp", true);
			 * jpcap.setFilter("tcp and src 172.16.101.99", true);
			 * captor.setFilter("vlan", true); 
			 * 如何过滤HTTP？
			 */
			
			logger.debug("filter is :" + filter);
			
			if(filter != null && !"".equals(filter)){
				captor.setFilter(filter, true);
			} 
			captor.loopPacket(-1, this.packetHandler);//此处循环读文件，读完一个文件方法结束
			
			
			while(fileRealSerial) {// 此处决定读入单个文件的所有packet后，是否阻塞主线程，当前文件处理完时，跳出循环。
//				if(complete) {
				if(tcp_complete && udp_complete){
					break;
				}
				Thread.sleep(1000);
			}
			

			
			
		} catch (IOException e) {
			logger.error("Some IOException",e);
		} 
//		catch (InterruptedException e) {
//			logger.error("Some InterruptedException",e);
//		} 
		catch (Exception e){
			logger.error("Some Exception",e);
		}
	}
	/**
	  *  从多个pcap文件读取抓包
	 * @param filePaths
	 */
	public void captureFromFiles(String[] filePaths,PacketHandler packetHandler){
		for(String filePath : filePaths){
//			this.complete = false;
			this.tcp_complete = false;
			this.udp_complete = false;
			this.captureFromFile(filePath,packetHandler);
		}
	}
	
	/**
	 * 遍历一个目录
	 */
	public void captureFromDirectory(String dirPath,PacketHandler packetHandler){
		
		FilenameFilter filenameFilter = new FilenameFilter(){
			@Override
			public boolean accept(File file, String name) {//这里的name属性，表示文件或文件夹的名字，
				                                           //可以直接根据名字做前缀、后缀以及正则表达式等进行过滤，
				                                           //如：return name.startsWith("ra");
				return name.matches("(.*\\.pcap)|(.*\\.cap)");
				//return name.contains(".pcap");//性能是否不如正则表达式?
			}
		};
		
		File dir = new File(dirPath);
		if( !dir.isDirectory() ){
			logger.error("the input is not a directory ");
			return;
		}
		
		File[] files = dir.listFiles(filenameFilter);
		
		logger.info("the fileNumber is : "+files.length);
		//logger.info("the dir total length is : "+dir.length());//8192
		
		int totalBytes = 0;
		for(File file : files){
			totalBytes += file.length();
			logger.info("the < "+file.getName()+" > file.lentgh is : "+file.length());
		}
		logger.info("the dir total length is : "+totalBytes);
		
		int dealTotalBytes = 0;
		for(File file : files){
			dealTotalBytes += file.length();
//			this.complete = false;
			
			this.tcp_complete = false;
			this.udp_complete = false;
			
			this.captureFromFile(file.getAbsolutePath(),packetHandler);
			
			DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance();
			decimalFormat.setMaximumFractionDigits(2);
			decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
			float progressNum = (float)dealTotalBytes/(float)totalBytes * 100;
			
			String progressStr = decimalFormat.format(progressNum);
			logger.info("the pcap deal rate of progress is : "+ progressStr +" %");
		}
		
	}
	
	
	/**
	 * deviceInfo 应该做到可以适配 mac或name
	 * mac eg: 2CF0EE057A0A
	 * @param deviceInfo
	 * @return
	 */
	private NetworkInterface findDevice(String deviceInfo){
		jpcap.NetworkInterface[] devices = JpcapCaptor.getDeviceList();
		logger.debug("device sum is :"+devices.length);
		
		if(devices.length < 0)
	        return null;
		
		for(jpcap.NetworkInterface device : devices){        	
        	String mac = CommonUtils.bytesToHexString(device.mac_address);
        	if(mac.equalsIgnoreCase(deviceInfo)){
        		logger.info("find the device with deviceinfo :"+deviceInfo);//Log
        		return device;
        	}
        	
        	if(device.name.equalsIgnoreCase(deviceInfo)){
        		return device;
        	}
        }
		logger.error("can not find the device with input deviceinfo :"+deviceInfo);//Log
		logger.error("the device list is :");//Log
		int i = 0;
		for(jpcap.NetworkInterface device : devices){        	
        	String mac = CommonUtils.bytesToHexString(device.mac_address);
        	logger.error("    device "+i++ +" : <"+device.name+"> <"+mac+">");     //Log	
        }
		
		return null;
	
	}
	
	/**
	 * 目前采用的方法其实并不那么美丽，但是确实解决了Windows下的Dll依赖问题
	 * 
	 * 判断当前JDK/JRE的操作位数，sun.arch.data.model=32 或者64,然后操作位数之后，copy对应的Jpcap.dll文件到根目录下
	 * 
	 * Jpcap运行时会去sun.boot.library.path查找Jpcap.dll动态链接库
	 * 默认如果32位JDK/JRE，会去SysWow64下查找，64位JDK/JRE会去System32下查找（JRE的bin目录也是查找路径）
	 * 当前工程根目录也是查找路径，所以这里用该方法决定将哪个Jpcap.dll放到路径下
	 * 
	 * Win/Linux都在sun.boot.library.path查找动态链接库dll或so，win通常是对应jre下的bin目录下，Linux通常是jre下的lib下的amd64目录
	 */
	public void prepareDependantDll(){
		String os_name = (String) System.getProperties().get("os.name");
		String architecture=String.valueOf(System.getProperties().get("sun.arch.data.model"));//sun.arch.data.model表示JDK/JRE的操作位数，os.arch表示操作系统的操作位数
		logger.info("os.name : "+os_name );
		logger.info("sun.arch.data.model : "+architecture);
		String sun_boot_library_path =  (String) System.getProperties().get("sun.boot.library.path");
		logger.info("sun.boot.library.path : "+sun_boot_library_path);
		if(!os_name.toLowerCase().contains("window")) {
			//linux时处理,仅支持64位操作系统
			

			String soPath = JCaptures.class.getResource("/").getPath()+"jpcapdll/64/libjpcap.so";
			File soSource = new File(soPath);
			if(!soSource.exists()){
				logger.error("can not find libjpcap.so file in path :"+soPath);
				return;
			}
			
			File dest = new File(sun_boot_library_path+File.separator+"libjpcap.so");
			if(!dest.exists()) {
				try {
					//Files.copy(is, dest.toPath());//(Files class use need Java1.7+)
					Files.copy(soSource.toPath(), dest.toPath());//(Files class use need Java1.7+)
					logger.info("file does not exist in path : "+dest.toPath()+" ,copy a new one!");
				} catch (IOException e) {
					logger.error("Files deal exception",e);
				}
			}else {
				logger.info("file already exist in path : "+dest.toPath());
			}
			return;
		}
		
		//windows时处理
		String dllPath = null;
		if("32".endsWith(architecture))
		    dllPath = JCaptures.class.getResource("/").getPath()+"jpcapdll/32/Jpcap.dll";
		else
		    dllPath = JCaptures.class.getResource("/").getPath()+"jpcapdll/64/Jpcap_x64.dll";

		dllPath = dllPath.replace("%20", " ");
		dllPath = dllPath.replace("%23", "#");
		//dllPath = dllPath.substring(1);
		
		File source = new File(dllPath);
		if(!source.exists()){
			logger.error("can not find jpcap.dll file in path :"+dllPath);
			return;
		}
		
		//File dest = new File("Jpcap.dll");
		File dest = new File(sun_boot_library_path+File.separator+"Jpcap.dll");
		if(dest.exists()){
			//dest.delete();//每次删除是需要重新复制32/64对应的dll，因为从名称无法判断32/64
			logger.info("file already exist in path : "+dest.toPath()+" !");
		}else{
			try {
				Files.copy(source.toPath(), dest.toPath());//(Files class use need Java1.7+)
				logger.info("file does not exist in path : "+dest.toPath()+" ,copy a new one!");
			} catch (IOException e) {
				logger.error("Files deal exception",e);
			}
		}
			
		
	}
	

	
	/**
	 * TODO TODO 使用NativeLibrary不生效....
	 */
	@Deprecated
	public void prepareDependantDll2(){
	    Properties props = System.getProperties();
		String architecture=String.valueOf(props.get("sun.arch.data.model"));//sun.arch.data.model=32 或者64
		 
		 

		String dllPath = null;

		if("32".endsWith(architecture))
		    dllPath = JCaptures.class.getResource("/").getPath()+"jpcapdll/32/Jpcap.dll";
		else
		    dllPath = JCaptures.class.getResource("/").getPath()+"jpcapdll/64/Jpcap_x64.dll";

	    dllPath = dllPath.replace("%20", " ");
		dllPath = dllPath.replace("%23", "#");
		//dllPath = dllPath.substring(1);
		 
		logger.info(dllPath);
		//dllPath=dllPath.substring(1,dllPath.length()).replace("/", "\\");
		// File file = new File(dllPath + "/Jpcap.dll");
		File source = new File(dllPath);
		if(!source.exists())
			logger.info(source.getAbsolutePath()+" do not exist!");
		 
		File dest = new File(System.getProperty("user.dir")+"\\tmp\\Jpcap.dll");
		if(dest.exists())
			dest.delete();//每次删除是需要重新复制32/64对应的dll，因为从名称无法判断32/64
		 
		try {
		    new File(System.getProperty("user.dir")+"\\tmp").mkdir();
			Files.copy(source.toPath(), dest.toPath());
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		 
		 
		logger.info(dllPath);
		logger.info(source.getAbsolutePath());
		logger.info(dest.getAbsolutePath());
		 
		NativeLibrary.addSearchPath("jpcap", System.getProperty("user.dir")+"\\tmp");//jna.jar:com.sun.jna.NativeLibrary
		//System.load(dest.getAbsolutePath());
		System.loadLibrary("jpcap");
		//System.out.println(props.get("java.library.path"));
	}



	

}
